/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.uml_generator.manager.impl;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import com.genia.toolbox.basics.bean.DisplayObject;
import com.genia.toolbox.basics.bean.impl.ConstantDelimiter;
import com.genia.toolbox.basics.exception.technical.TechnicalIOException;
import com.genia.toolbox.basics.process.ProxyHelper;
import com.genia.toolbox.uml_generator.Constants;
import com.genia.toolbox.uml_generator.manager.ModelDecorator;
import com.genia.toolbox.uml_generator.model.AbstractJavaObject;
import com.genia.toolbox.uml_generator.model.AbstractObject;

/**
 * implementation of {@link ModelDecorator}.
 */
public class ModelDecoratorImpl
    extends AbstractManager
    implements ModelDecorator
{

  /**
   * interface that tag elements that has been decorated as generics.
   */
  public interface GenericObject
  {
    /**
     * returns the list of generic types of the object.
     * 
     * @return the list of generic types of the object
     */
    public AbstractJavaObject[] getGenericTypes();
  }

  /**
   * the method that must continue through the proxy.
   */
  private static final List<String> METHODS_TO_PROXY = Arrays.asList("getObject", "getName", "getQualifiedName", "getPackageName", "umlId", "getDefaultValue");



  /**
   * returns the name suffix to add for a generic entity.
   * 
   * @param types
   *          the generic types
   * @return the name suffix to add for a generic entity
   * @throws TechnicalIOException
   *           if an error occured
   */
  private String getNameSuffixForGenerics(AbstractJavaObject... types)
      throws TechnicalIOException
  {
    return getCollectionManager().join(types, new ConstantDelimiter("<", ",", ">"), new DisplayObject<AbstractJavaObject>()
    {
      public void append(Appendable appendable, AbstractJavaObject object)
          throws IOException
      {
        appendable.append(object.getQualifiedName());
      }
    });
  }

  /**
   * a {@link DecoratorMethodInterceptor} for handling Java 5 generic type.
   */
  private class GenericsDecoratorMethodInterceptor
      extends DecoratorMethodInterceptor
  {

    /**
     * All generated proxied methods call this method instead of the original
     * method. The original method may either be invoked by normal reflection
     * using the Method object, or by using the MethodProxy (faster).
     * 
     * @param obj
     *          "this", the enhanced object
     * @param method
     *          intercepted Method
     * @param args
     *          argument array; primitive types are wrapped
     * @param proxy
     *          used to invoke super (non-intercepted method); may be called as
     *          many times as needed
     * @throws Throwable
     *           any exception may be thrown; if so, super method will not be
     *           invoked
     * @return any value compatible with the signature of the proxied method.
     *         Method returning void will ignore this value.
     * @see net.sf.cglib.proxy.MethodProxy
     * @see net.sf.cglib.proxy.MethodInterceptor#intercept(java.lang.Object,
     *      java.lang.reflect.Method, java.lang.Object[],
     *      net.sf.cglib.proxy.MethodProxy)
     * @see com.genia.toolbox.uml_generator.manager.impl.ModelDecoratorImpl.DecoratorMethodInterceptor#intercept(java.lang.Object,
     *      java.lang.reflect.Method, java.lang.Object[],
     *      net.sf.cglib.proxy.MethodProxy)
     */
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
        throws Throwable
    {
      if (method.getName().equals("getGenericTypes")) {
        return types;
      }
      return super.intercept(obj, method, args, proxy);
    }

    /**
     * the array of type of the generic object.
     */
    private AbstractJavaObject[] types;



    /**
     * constructor.
     * 
     * @param originalObject
     *          the original object that is decorated
     * @param types
     *          the array of type of the generic object
     * @throws TechnicalIOException
     *           if an error occured
     */
    private GenericsDecoratorMethodInterceptor(AbstractObject originalObject, AbstractJavaObject... types)
        throws TechnicalIOException
    {
      super(originalObject, getNameSuffixForGenerics(types), null);
      this.types = types;
    }
  }

  /**
   * the {@link MethodInterceptor} class that permits to decorate the object.
   */
  private class DecoratorMethodInterceptor
      implements MethodInterceptor
  {

    /**
     * the original object that is decorated.
     */
    private final transient AbstractObject originalObject;

    /**
     * the suffix to add to the name of the object.
     */
    private final transient String nameSuffix;

    /**
     * the suffix to add to the package of the object.
     */
    private final transient String packageNameSuffix;



    /**
     * constructor.
     * 
     * @param originalObject
     *          the original object that is decorated
     * @param nameSuffix
     *          the suffix to add to the name of the object
     * @param packageNameSuffix
     *          the suffix to add to the package of the object
     */
    private DecoratorMethodInterceptor(AbstractObject originalObject, String nameSuffix, String packageNameSuffix)
    {
      this.originalObject = originalObject;
      this.nameSuffix = (nameSuffix == null ? "" : nameSuffix);
      this.packageNameSuffix = packageNameSuffix;
    }



    /**
     * All generated proxied methods call this method instead of the original
     * method. The original method may either be invoked by normal reflection
     * using the Method object, or by using the MethodProxy (faster).
     * 
     * @param obj
     *          "this", the enhanced object
     * @param method
     *          intercepted Method
     * @param args
     *          argument array; primitive types are wrapped
     * @param proxy
     *          used to invoke super (non-intercepted method); may be called as
     *          many times as needed
     * @throws Throwable
     *           any exception may be thrown; if so, super method will not be
     *           invoked
     * @return any value compatible with the signature of the proxied method.
     *         Method returning void will ignore this value.
     * @see net.sf.cglib.proxy.MethodProxy
     * @see net.sf.cglib.proxy.MethodInterceptor#intercept(java.lang.Object,
     *      java.lang.reflect.Method, java.lang.Object[],
     *      net.sf.cglib.proxy.MethodProxy)
     */
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy)
        throws Throwable
    {
      if (METHODS_TO_PROXY.contains(method.getName())) {
        if (method.getName().equals("umlId")) {
          String umlId = (String) proxy.invoke(originalObject, args);
          if (nameSuffix == null) {
            umlId += '\\' + nameSuffix;
          }
          if (packageNameSuffix == null) {
            umlId += '/' + packageNameSuffix;
          }
          return umlId;
        }
        else if (method.getName().equals("getName")) {
          String name = (String) proxy.invoke(originalObject, args);
          if (nameSuffix != null) {
            name += nameSuffix;
          }
          return name;
        }
        else if (method.getName().equals("getPackageName")) {
          String packageName = (String) proxy.invoke(originalObject, args);
          if (packageNameSuffix != null) {
            if (!"".equals(packageName)) {
              packageName += ".";
            }
            packageName += packageNameSuffix;
          }
          return packageName;
        }
        else {
          return proxy.invokeSuper(obj, args);
        }
      }
      else {
        return proxy.invoke(originalObject, args);
      }
    }
  }



  /**
   * generate a decorated {@link AbstractObject}.
   * 
   * @param <T>
   *          the type of the object to decorate
   * @param originalObject
   *          the object to decorate
   * @param methodInterceptor
   *          the {@link MethodInterceptor} to use
   * @param interfaces
   *          the list of interfaces the the proxy must implements
   * @return a decorated object or <code>null</code> if originalObject is
   *         <code>null</code>
   * @see com.genia.toolbox.uml_generator.manager.ModelDecorator#getDecoratedObject(com.genia.toolbox.uml_generator.model.AbstractObject,
   *      java.lang.String, java.lang.String)
   */
  @SuppressWarnings("unchecked")
  public <T extends AbstractObject> T getDecoratedObject(final T originalObject, MethodInterceptor methodInterceptor, Class... interfaces)
  {
    if (originalObject == null) {
      return null;
    }
    return (T) ProxyHelper.getProxyFactory(originalObject.getUndecoraredObject().getClass(), interfaces).newInstance(methodInterceptor);
  }



  /**
   * generate a decorated {@link AbstractObject}.
   * 
   * @param <T>
   *          the type of the object to decorate
   * @param originalObject
   *          the object to decorate
   * @param nameSuffix
   *          the suffix to add to the name of the object
   * @param packageNameSuffix
   *          the package suffix to add to the package of the object
   * @return a decorated object or <code>null</code> if originalObject is
   *         <code>null</code>
   * @see com.genia.toolbox.uml_generator.manager.ModelDecorator#getDecoratedObject(com.genia.toolbox.uml_generator.model.AbstractObject,
   *      java.lang.String, java.lang.String)
   */
  public <T extends AbstractObject> T getDecoratedObject(final T originalObject, String nameSuffix, String packageNameSuffix)
  {
    if (originalObject == null) {
      return null;
    }
    return getDecoratedObject(originalObject, new DecoratorMethodInterceptor(originalObject, nameSuffix, packageNameSuffix));
  }



  /**
   * generate a decorated {@link AbstractObject} for the delegate.
   * 
   * @param <T>
   *          the type of the object to decorate
   * @param originalObject
   *          the object to decorate
   * @return a decorated object
   */
  public <T extends AbstractObject> T getDelegateObject(T originalObject)
  {
    return getDecoratedObject(originalObject, Constants.DELEGATE_SUFFIX, Constants.IMPLEMENTATION_PACKAGE_SUFFIX);
  }



  /**
   * generate a decorated {@link AbstractObject} for the mapping.
   * 
   * @param <T>
   *          the type of the object to decorate
   * @param originalObject
   *          the object to decorate
   * @return a decorated object
   * @see com.genia.toolbox.uml_generator.manager.ModelDecorator#getMappingObject(com.genia.toolbox.uml_generator.model.AbstractObject)
   */
  public <T extends AbstractObject> T getMappingObject(T originalObject)
  {
    return getDecoratedObject(originalObject, Constants.MAPPING_SUFFIX, Constants.MAPPING_PACKAGE_SUFFIX);
  }



  /**
   * generate a decorated {@link AbstractObject} for the implementation.
   * 
   * @param <T>
   *          the type of the object to decorate
   * @param originalObject
   *          the object to decorate
   * @return a decorated object
   * @see com.genia.toolbox.uml_generator.manager.ModelDecorator#getImplementationObject(com.genia.toolbox.uml_generator.model.AbstractObject)
   */
  public <T extends AbstractObject> T getImplementationObject(T originalObject)
  {
    return getDecoratedObject(originalObject, Constants.IMPLEMENTATION_SUFFIX, Constants.IMPLEMENTATION_PACKAGE_SUFFIX);
  }



  /**
   * returns a decorated {@link AbstractObject} that is a generic version of an
   * existent object.
   * 
   * @param <T>
   *          the type of the object to decorate
   * @param originalObject
   *          the object to decorate
   * @param types
   *          the type to add as generic parameters
   * @return a decorated object
   * @throws TechnicalIOException
   *           when an I/O error occurred
   * @see com.genia.toolbox.uml_generator.manager.ModelDecorator#getGenericObject(com.genia.toolbox.uml_generator.model.AbstractObject,
   *      com.genia.toolbox.uml_generator.model.AbstractJavaObject)
   */
  public <T extends AbstractObject> T getGenericObject(T originalObject, AbstractJavaObject... types)
      throws TechnicalIOException
  {
    return getDecoratedObject(originalObject, new GenericsDecoratorMethodInterceptor(originalObject, types), GenericObject.class);
  }



  /**
   * generate a decorated {@link AbstractObject} for the internal object.
   * 
   * @param <T>
   *          the type of the object to decorate
   * @param originalObject
   *          the object to decorate
   * @return a decorated object
   */
  public <T extends AbstractObject> T getInternalObject(T originalObject)
  {
    return getDecoratedObject(originalObject, Constants.INTERNAL_SUFFIX, null);
  }



  /**
   * generate a decorated {@link AbstractObject} for a plurar.
   * 
   * @param <T>
   *          the type of the object to decorate
   * @param originalObject
   *          the object to decorate
   * @return a decorated object
   * @see com.genia.toolbox.uml_generator.manager.ModelDecorator#getPluralObject(com.genia.toolbox.uml_generator.model.AbstractObject)
   */
  public <T extends AbstractObject> T getPluralObject(T originalObject)
  {
    return getDecoratedObject(originalObject, Constants.PLURAL_SUFFIX, null);
  }



  /**
   * generate a decorated {@link AbstractObject} for the dependency object.
   * 
   * @param <T>
   *          the type of the object to decorate
   * @param originalObject
   *          the object to decorate
   * @return a decorated object
   * @see com.genia.toolbox.uml_generator.manager.ModelDecorator#getDependencyObject(com.genia.toolbox.uml_generator.model.AbstractObject)
   */
  public <T extends AbstractObject> T getDependencyObject(T originalObject)
  {
    return getDecoratedObject(originalObject, Constants.DEPENDENCY_SUFFIX, Constants.DEPENDENCY_PACKAGE_SUFFIX);
  }



  /**
   * generate a decorated {@link AbstractObject} for the message object.
   * 
   * @param <T>
   *          the type of the object to decorate
   * @param originalObject
   *          the object to decorate
   * @return a decorated object
   * @see com.genia.toolbox.uml_generator.manager.ModelDecorator#getMessagesObject(com.genia.toolbox.uml_generator.model.AbstractObject)
   */
  public <T extends AbstractObject> T getMessagesObject(T originalObject)
  {
    return getDecoratedObject(originalObject, Constants.MESSAGES_SUFFIX, null);
  }

}
